package dian.qing.li.demo.security.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.pactera.redis.utils.RedisUtils;
import dian.qing.li.demo.security.bean.AuthParam;
import dian.qing.li.demo.security.constants.CodeKey;
import dian.qing.li.demo.security.constants.Constants;
import dian.qing.li.demo.security.constants.DataType;
import dian.qing.li.demo.security.constants.RedisKey;
import dian.qing.li.demo.security.dto.*;
import dian.qing.li.demo.security.mapper.AuthRequestMapper;
import dian.qing.li.demo.security.mapper.AuthorityMapper;
import dian.qing.li.demo.security.mapper.RoleAuthorityMapper;
import dian.qing.li.demo.security.service.AuthorityService;
import dian.qing.li.demo.security.service.RoleService;
import dian.qing.li.demo.security.service.UserService;
import dian.qing.li.demo.security.utils.Assert;
import dian.qing.li.demo.security.utils.ObjUtils;
import dian.qing.li.demo.security.utils.SysUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 权限表 服务实现类
 * </p>
 *
 * @author admin
 * @since 2020-07-01
 */
@Service
public class AuthorityServiceImpl extends ServiceImpl<AuthorityMapper, AuthorityDto> implements AuthorityService {

    @Autowired
    private RoleService roleService;

    @Autowired
    private UserService userService;

    @Autowired
    private RoleAuthorityMapper roleAuthorityMapper;

    @Autowired
    private AuthRequestMapper authRequestMapper;

    @Override
    public List<AuthorityDto> tree(AuthParam param) {
        return null;
    }

    @Override
    public AuthorityDto find(Integer id) {
        Assert.isNotNull(id, "[id]不能为空");
        AuthorityDto auth = this.getById(id);
        Assert.isNotNull(auth, StringUtils.join("不存在[", id, "]权限详情"));
        return null;
    }

    @Override
    public AuthorityDto find(String authCode) {
        Assert.isNotBlank(authCode, "[authCode]不能为空");
        AuthorityDto find = new AuthorityDto();
        find.setAuthCode(authCode.trim());
        QueryWrapper<AuthorityDto> query = new QueryWrapper<>(find);
        AuthorityDto auth = this.getOne(query);
        Assert.isNotNull(auth, StringUtils.join("不存在[", authCode, "]权限详情"));
        return auth;
    }

    @Override
    public List<AuthorityDto> treeByRoleCode(String roleCode) {
        Assert.isNotBlank(roleCode, "[roleCode]不能为空");
        RoleDto role = roleService.find(roleCode);
        QueryWrapper<RoleAuthorityDto> queryRoleAuth = new QueryWrapper<>();
        queryRoleAuth.eq("role_code", role.getRoleCode());
        List<RoleAuthorityDto> roleAuthorityDtos = roleAuthorityMapper.selectList(queryRoleAuth);
        Set<String> authCodeSet = roleAuthorityDtos.stream().map(RoleAuthorityDto::getAuthCode).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(authCodeSet)) {
            return Lists.newArrayList();
        }
        QueryWrapper<AuthorityDto> query = new QueryWrapper<>();
        query.in("auth_code", authCodeSet);
        query.orderByAsc("CHARACTER_LENGTH(full_code) - CHARACTER_LENGTH(REPLACE(full_code,'/',''))").orderByAsc("sort");
        return this.list(query);
    }

    @Override
    public boolean inserted(AuthorityDto inserted) {
        Assert.isNotNull(inserted, "[inserted]不能为空");
        // 必填校验
        String[] check = {"authName", "authTitle"};
        ObjUtils.isNotNull(inserted, true, check);
        // 字符串属性trim
        ObjUtils.trim(inserted);
        // 父级
        String authParent = inserted.getAuthParent();
        if (StringUtils.isBlank(authParent)) {
            authParent = Constants.PARENT_ROOT;
            inserted.setAuthParent(authParent);
        }
        // 重名校验
        this.checkDuplication(inserted);
        // 标准属性设置
        inserted.setId(null);
        inserted.setDeleted(null);
        inserted.setCreateBy(SysUtils.getUsername());
        inserted.setUpdateBy(inserted.getCreateBy());
        // 生成编码
        String redisKey = RedisUtils.redisKey(RedisKey.CODE, CodeKey.AUTHORITY);
        long init = 0;
        if (!RedisUtils.hasKey(redisKey)) {
            Long max = this.getBaseMapper().initCode();
            init = max == null ? 0 : max;
        }
        String numCode = RedisUtils.getNumCode(redisKey, DataType.AUTHORITY.getCode(), init);
        inserted.setAuthCode(numCode);
        this.setFullPath(inserted, authParent);
        return this.save(inserted);
    }

    /**
     * 设置全路径
     *
     * @author: liqingdian
     */
    private void setFullPath(AuthorityDto auth, String authParent) {
        // 拼接全路径
        if (StringUtils.equals(Constants.PARENT_ROOT, authParent)) {
            auth.setFullCode(Joiner.on("").join(Constants.SEPARATOR_PATH, auth.getAuthCode()));
            auth.setFullName(Joiner.on("").join(Constants.SEPARATOR_PATH, auth.getAuthName()));
        } else {
            AuthorityDto parent = this.find(authParent);
            auth.setFullCode(Joiner.on("").join(parent.getFullCode(), Constants.SEPARATOR_PATH, auth.getAuthCode()));
            auth.setFullName(Joiner.on("").join(parent.getFullName(), Constants.SEPARATOR_PATH, auth.getAuthName()));
        }
    }

    /**
     * 重名校验
     *
     * @author: liqingdian
     */
    private void checkDuplication(AuthorityDto auth) {
        // 同层级下不允许authName重名
        AuthorityDto find = new AuthorityDto();
        find.setAuthParent(auth.getAuthParent());
        QueryWrapper<AuthorityDto> query = new QueryWrapper<>(find);
        if (auth.getId() != null) {
            query.ne("id", auth.getId());
        }
        find.setAuthName(auth.getAuthName());
        int count = this.count(query);
        Assert.isFalse(count > 0, StringUtils.join("[", auth.getAuthName(), "]已经存在，请重新输入"));
    }

    @Transactional
    @Override
    public boolean updated(AuthorityDto updated) {
        Assert.isNotNull(updated, "[updated]不能为空");
        Assert.isNotNull(updated.getId(), "[updated.id]不能为空");
        Assert.isNotBlank(updated.getAuthName(), "[updated.authName]不能为空");
        // 字符串属性trim
        ObjUtils.trim(updated);
        // 父级
        String authParent = updated.getAuthParent();
        if (StringUtils.isBlank(authParent)) {
            authParent = Constants.PARENT_ROOT;
            updated.setAuthParent(authParent);
        }
        // 设置不允许修改字段为空
        updated.setAuthCode(null);
        updated.setDeleted(null);
        // 重名校验
        this.checkDuplication(updated);
        // 设置更新属性
        updated.setUpdateBy(SysUtils.getUsername());
        // 是否禁用，是否删除
        Integer disabled = updated.getDisabled();
        if (disabled != null) {
            if (Constants.YES == disabled) {
                updated.setDisabled(Constants.YES);
            } else {
                updated.setDisabled(Constants.NO);
            }
        }
        // 是否重新生成全路径
        boolean changeParent = false;
        AuthorityDto history = this.find(updated.getId());
        if (!StringUtils.equals(history.getAuthParent(), authParent) || !StringUtils.equals(updated.getAuthName(), history.getAuthName())) {
            this.setFullPath(updated, authParent);
            changeParent = true;
        }
        boolean success = this.updateById(updated);
        // 更新子节点全路径
        if (changeParent) {
            UpdateWrapper<AuthorityDto> updateFullPath = new UpdateWrapper<>();
            updateFullPath.set("full_code", Joiner.on("").join("REPLACE(full_code,", history.getFullCode(), Constants.SEPARATOR_PATH, ",", updated.getFullCode(), Constants.SEPARATOR_PATH, ")"));
            updateFullPath.set("full_name", Joiner.on("").join("REPLACE(full_name,", history.getFullName(), Constants.SEPARATOR_PATH, ",", updated.getFullName(), Constants.SEPARATOR_PATH, ")"));
            updateFullPath.likeRight("full_code", Joiner.on("").join(history.getFullCode(), Constants.SEPARATOR_PATH));
            this.baseMapper.update(null, updateFullPath);
        }
        return success;
    }

    @Override
    public boolean disabled(Integer id, boolean disabled) {
        AuthorityDto auth = this.find(id);
        if (disabled) {
            // 存在未禁用子节点不允许禁用
            AuthorityDto find = new AuthorityDto();
            find.setAuthParent(auth.getAuthCode());
            find.setDisabled(Constants.NO);
            int count = this.count(new QueryWrapper<>(find));
            Assert.isFalse(count > 0, "存在未禁用的子节点，禁用失败");
            // 绑定请求资源的权限不允许删除
            QueryWrapper<AuthRequestDto> queryAuthReq = new QueryWrapper<>();
            queryAuthReq.eq("auth_code", auth.getAuthCode());
            Integer reqCount = authRequestMapper.selectCount(queryAuthReq);
            Assert.isFalse(reqCount > 0, "存在绑定的请求资源，禁用失败");
        }
        AuthorityDto updated = new AuthorityDto();
        updated.setId(id);
        updated.setUpdateBy(SysUtils.getUsername());
        int yes = disabled ? Constants.YES : Constants.NO;
        updated.setDisabled(yes);
        boolean success = true;
        if (yes != auth.getDisabled()) {
            success = this.updateById(updated);
        }
        if (disabled) {
            // 刷新用户缓存
            this.refreshUser(auth);
        }
        return success;
    }

    @Transactional
    @Override
    public boolean deleted(Integer id) {
        AuthorityDto auth = this.find(id);
        // 存在子节点不允许删除
        AuthorityDto find = new AuthorityDto();
        find.setAuthParent(auth.getAuthCode());
        int count = this.count(new QueryWrapper<>(find));
        Assert.isFalse(count > 0, "存在未删除的子节点，删除失败");

        // 绑定请求资源的权限不允许删除
        QueryWrapper<AuthRequestDto> queryAuthReq = new QueryWrapper<>();
        queryAuthReq.eq("auth_code", auth.getAuthCode());
        Integer reqCount = authRequestMapper.selectCount(queryAuthReq);
        Assert.isFalse(reqCount > 0, "存在绑定的请求资源，删除失败");

        // 删除角色-权限表
        UpdateWrapper<RoleAuthorityDto> deleteRoleAuth = new UpdateWrapper<>();
        deleteRoleAuth.set(Constants.DELETE_FIELD, Constants.YES);
        deleteRoleAuth.eq("auth_code", auth.getAuthCode());
        roleAuthorityMapper.update(null, deleteRoleAuth);

        AuthorityDto deleted = new AuthorityDto();
        deleted.setId(id);
        deleted.setDeleted(Constants.YES);
        deleted.setUpdateBy(SysUtils.getUsername());
        boolean success = this.updateById(deleted);
        // 刷新用户缓存
        this.refreshUser(auth);
        return success;
    }

    @Override
    public boolean bindRequest(Integer id, List<RequestDto> requests) {
        if (CollectionUtils.isNotEmpty(requests)) {
            AuthorityDto auth = this.find(id);
            List<AuthRequestDto> insertList = requests.stream()
                    .filter(a -> StringUtils.isNotBlank(a.getReqMethod()) && StringUtils.isNotBlank(a.getReqValue()))
                    .map(a -> {
                        AuthRequestDto inserted = new AuthRequestDto();
                        inserted.setAuthCode(auth.getAuthCode());
                        inserted.setReqMethod(a.getReqMethod());
                        inserted.setReqValue(a.getReqValue());
                        return inserted;
                    }).distinct().collect(Collectors.toList());
            int count = 0;
            for (AuthRequestDto inserted : insertList) {
                count += authRequestMapper.insert(inserted);
            }
            String redisKey = RedisUtils.redisKey(RedisKey.AUTH_REQUEST_LIST);
            if (RedisUtils.hasKey(redisKey)) {
                RedisUtils.delete(redisKey);
            }
            return count > 0;
        }
        return false;
    }

    @Override
    public boolean unbindRequest(Integer id, List<RequestDto> requests) {
        if (CollectionUtils.isNotEmpty(requests)) {
            AuthorityDto auth = this.find(id);
            List<AuthRequestDto> insertList = requests.stream()
                    .filter(a -> StringUtils.isNotBlank(a.getReqMethod()) && StringUtils.isNotBlank(a.getReqValue()))
                    .map(a -> {
                        AuthRequestDto inserted = new AuthRequestDto();
                        inserted.setAuthCode(auth.getAuthCode());
                        inserted.setReqMethod(a.getReqMethod());
                        inserted.setReqValue(a.getReqValue());
                        return inserted;
                    }).distinct().collect(Collectors.toList());
            int count = 0;
            UpdateWrapper<AuthRequestDto> deleted;
            for (AuthRequestDto update : insertList) {
                deleted = new UpdateWrapper<>(update);
                deleted.set(Constants.DELETE_FIELD, Constants.YES);
                count += authRequestMapper.update(null, deleted);
            }
            String redisKey = RedisUtils.redisKey(RedisKey.AUTH_REQUEST_LIST);
            if (RedisUtils.hasKey(redisKey)) {
                RedisUtils.delete(redisKey);
            }
            return count > 0;
        }
        return false;
    }

    /**
     * 刷新用户缓存
     *
     * @param auth 权限
     * @author: liqingdian
     */
    private void refreshUser(AuthorityDto auth) {
        QueryWrapper<RoleAuthorityDto> queryRoleAuth = new QueryWrapper<>();
        queryRoleAuth.select("role_code");
        queryRoleAuth.eq("auth_code", auth.getAuthCode());
        queryRoleAuth.groupBy("role_code");
        List<RoleAuthorityDto> roleAuthorityDtos = roleAuthorityMapper.selectList(queryRoleAuth);
        Set<String> roleCodeSet = roleAuthorityDtos.stream().map(RoleAuthorityDto::getRoleCode).collect(Collectors.toSet());
        Set<String> userCodeSet = Sets.newHashSet();
        for (String roleCode : roleCodeSet) {
            List<UserDto> userDtos = userService.listByRole(roleCode);
            Set<String> userSet = userDtos.stream().map(UserDto::getUserCode).collect(Collectors.toSet());
            userCodeSet.addAll(userSet);
        }
        for (String userCode : userCodeSet) {
            userService.refreshRedis(userCode, false);
        }
    }
}
